﻿/**
 * Licensed under the MIT License
 *
 * Copyright (c) 2010 Alexandre Croiseaux
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 */
package marcel.media.video
{
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.filters.GlowFilter;
	import flash.net.URLLoader;
	import flash.net.URLRequest;
	import flash.text.AntiAliasType;
	import flash.text.GridFitType;
	import flash.text.TextField;
	import flash.text.TextFieldAutoSize;
	import flash.text.TextFormat;
	import flash.text.TextFormatAlign;
	import marcel.debug.*;
	import marcel.events.VideoEvent;
	import marcel.media.video.VideoDisplay;
	
	
	/**
	* @eventType marcel.events.VideoEvent.SUBTITLES_READY
	*/
	[Event(name="onVideoSubtitlesReady", type="marcel.events.VideoEvent")]
	
	/**
	 * Class providing SRT support for flash video
	 * @author Alexandre Croiseaux
	 * @see marcel.media.video.VideoDisplay
	 */
	public class VideoSubtitles extends EventDispatcher
	{
		//--------------------------------------------------------------------------
		//
		//  Private vars
		//
		//--------------------------------------------------------------------------
		private var _textFormat:TextFormat;
		private var _textField:TextField;
		private var _displayer:VideoDisplay;
		private var _subtitles:Array;
		private var _currentSubtitleId:int;
		private var _textFilter:GlowFilter;
		private var _ready:Boolean;
		
		
		//--------------------------------------------------------------------------
		//
		//  Constructor
		//
		//--------------------------------------------------------------------------
		/**
		 * Constructs a new VideoSubtitles instance. Use VideoDisplay.setSubtitles to link subtitles to video
		 * @param	fontSize	the size of the subtitles
		 * @param	fontColor	the color of the subtitles (black shadow)
		 * @param	fontName	the font to use
		 * @param	embedFonts	indicates if the fonts are embedded in the swf file
		 * @see marcel.media.video.VideoDisplay
		 */
		public function VideoSubtitles(fontSize:int = 12, fontColor:int = 0xFFFFFF, fontName:String = "Arial", embedFonts:Boolean = false)
		{
			_textFormat = new TextFormat(fontName, fontSize, fontColor, true);
			_textFormat.align = TextFormatAlign.CENTER;
			
			_textField = new TextField();
			_textField.defaultTextFormat = _textFormat;
			_textField.antiAliasType = AntiAliasType.ADVANCED;
			_textField.gridFitType = GridFitType.PIXEL;
			_textField.embedFonts = embedFonts;
			_textField.multiline = true;
			_textField.wordWrap = true;
			_textField.selectable = false;
			_textField.autoSize = TextFieldAutoSize.LEFT;
			_textFilter = new GlowFilter(0, 1, 3, 3, 20, 1);
			
			_subtitles = new Array();
			_ready = false;
		}
		
		
		//--------------------------------------------------------------------------
		//
		//  Public methods
		//
		//--------------------------------------------------------------------------
		/**
		 * Loads a specific SRT file
		 * @param	fileURI		URL of the SRT file
		 */
		public function loadSRTFile(fileURI:String):void
		{
			_ready = false;
			var uloader:URLLoader = new URLLoader();
			uloader.addEventListener(Event.COMPLETE, onLoadSRTFile);
			uloader.load(new URLRequest(fileURI));
		}
		
		/**
		 * Indicates SRT text to display, avoids external SRT file loading
		 * @param	srtContent
		 */
		public function setSRTContent(srtContent:String):void
		{
			_ready = false;
			parseSRTFile(srtContent);
		}
		
		
		//--------------------------------------------------------------------------
		//
		//  Internal methods
		//
		//--------------------------------------------------------------------------
		/**
		 * Indicates the associated VideoDisplay instance
		 * This method is internal and can only be called by the VideoDisplay class, use VideoDisplay.setSubtitles to link subtitles to video
		 * @param	vd	a VideoDisplay instance to associate with this subtitles
		 * @see marcel.media.video.VideoDisplay
		 * @private
		 */
		internal function setDisplayer(vd:VideoDisplay):void
		{
			_displayer = vd;
			_displayer.addChild(_textField);
			_displayer.addEventListener(VideoEvent.START, onVideoEvent);
			_displayer.addEventListener(VideoEvent.LOOP, onVideoEvent);
			_displayer.addEventListener(VideoEvent.SEEK, onVideoEvent);
			_displayer.addEventListener(VideoEvent.STOP, onVideoEvent);
			_displayer.addEventListener(VideoEvent.END, onVideoEvent);
			_displayer.addEventListener(VideoEvent.RESIZE, onVideoEvent);
			_displayer.addEventListener(VideoEvent.PLAYING, onVideoEvent);
			updateTextfieldSize(_displayer.getVideo().width, _displayer.getVideo().height);
		}
		
		/**
		 * Check if the subtitles are loaded and ready to use
		 * This method is internal and can only be called by the VideoDisplay class, listen to VideoEvent.SUBTITLES_READY to know when subtitles are ready
		 * @return	true id subtitles are ready, false otherwise
		 */
		internal function isReady():Boolean
		{
			return _ready;
		}
		
		
		//--------------------------------------------------------------------------
		//
		//  Private methods
		//
		//--------------------------------------------------------------------------
		private function updateTextfieldSize(videoWidth:int, videoHeight:int):void
		{
			_textField.width = videoWidth - 20;
			_textField.x = 10;
			_textField.y = _displayer.getVideo().height - _textField.height - 5;
		}
		
		private function setText(strText:String):void
		{
			_textField.text = strText;
			_textField.y = _displayer.getVideo().height - _textField.height - 5;
			_textField.filters = [_textFilter];
		}
		
		private function parseSRTFile(content:String):void
		{
			_subtitles = new Array();
			
			var buildNew:Boolean = true;
			var lines:Array = content.split(String.fromCharCode(10) + String.fromCharCode(13));
			var len:uint = lines.length;
			for (var i:uint = 0; i < len; i++)
			{
				var sbContents:Array = lines[i].split(String.fromCharCode(13));
				var id:int = int(sbContents[0]);
				var time:String = sbContents[1];
				var text:String = "";
				for (var j:int = 2; j < sbContents.length; j++) text += sbContents[j];
				var su:Subtitle = new Subtitle(id);
				su.setTimes(time);
				su.text = text;
				_subtitles.push(su);
			}
			
			_ready = true;
			dispatchEvent(new VideoEvent(VideoEvent.SUBTITLES_READY));
		}
		
		private function checkTags():void
		{
			var time:Number = Math.round(_displayer.getTime() * 1000);
			var len:int = _subtitles.length;
			for (var i:int = _currentSubtitleId; i < len; i++)
			{
				var su:Subtitle = _subtitles[i];
				if (time >= su.startTime && time <= su.endTime)
				{
					setText(su.text);
					_currentSubtitleId = i + 1;
					return;
				}
			}
			if (_currentSubtitleId > 0 && Subtitle(_subtitles[_currentSubtitleId-1]).endTime < time && _textField.length > 0) setText("");
			else if (_currentSubtitleId > 0 && _displayer.getTime()*1000 < Subtitle(_subtitles[_currentSubtitleId-1]).startTime)
			{
				setText("");
				_currentSubtitleId = 0;
			}
		}
		
		
		//--------------------------------------------------------------------------
		//
		//  Events
		//
		//--------------------------------------------------------------------------
		private function onVideoEvent(evt:VideoEvent):void
		{
			switch (evt.type)
			{
				case VideoEvent.RESIZE:
					updateTextfieldSize(evt.target.width, evt.target.height);
					break;
				case VideoEvent.START:
				case VideoEvent.LOOP:
					setText("");
					_currentSubtitleId = 0;
					break;
				case VideoEvent.STOP:
				case VideoEvent.END:
					setText("");
					break;
				case VideoEvent.PLAYING:
					checkTags();
					break;
				case VideoEvent.SEEK:
					_currentSubtitleId = 0;
					checkTags();
					break;
			}
		}
		
		private function onLoadSRTFile(evt:Event):void
		{
			parseSRTFile(evt.target.data.toString());
		}
	}
}


internal class Subtitle
{
	public var id:int;
	public var startTime:Number;
	public var endTime:Number;
	public var text:String;
	
	// Constructor
	public function Subtitle(id:int)
	{
		this.id = id;
	}
	
	
	// Public methods
	public function setTimes(strTime:String):void
	{
		// strTime looks like 00:00:03,754 --> 00:00:05,684
		var parts:Array = strTime.split(" --> ");
		startTime = getMsTime(parts[0]);
		endTime = getMsTime(parts[1]);
	}
	
	public function toString():String
	{
		return "[object Subtitle id='" + id + "'  " + startTime + " -> " + endTime + " " + text + "]";
	}
	
	// Private methods
	private function getMsTime(strTime:String):int
	{
		// strTime looks like 00:00:03,754
		var parts:Array = strTime.split(":");
		var hours:int = int(parts[0]);
		var minutes:int = int(parts[1]);
		var ms:int = int(parts[2].replace(",", ""));
		return (hours * 3600 * 1000) + (minutes * 60 * 1000) + ms;
	}
}
